/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "concurrent_task_controller.h"
#include <cinttypes>
#include <sys/resource.h>
#include <linux/sched.h>
#include <sched.h>
#include "rtg_interface.h"
#include "ipc_skeleton.h"
#include "concurrent_task_log.h"

using namespace OHOS::RME;

namespace OHOS {
namespace ConcurrentTask {

IMPLEMENT_SINGLE_INSTANCE(TaskController);

void TaskController::ReportData(uint32_t resType, int64_t value, const Json::Value& payload)
{
    pid_t uid = IPCSkeleton::GetInstance().GetCallingUid();
    bool IsSytemCall = CheckUid(uid);
    std::string strRequstType = payload["type"].asString();
    if (strRequstType.length() == 0) {
        CONCUR_LOGE("Get payload type err");
        return;
    }
    int requstType = GetRequestType(strRequstType);
    if (IsSytemCall) {
        DealSystemRequest(requstType, payload);
    } else {
        DealAppRequest(requstType, payload, uid);
    }
    PrintInfo();
}

void TaskController::QueryInterval(int queryItem, IntervalReply& queryRs)
{
    pid_t uid = IPCSkeleton::GetInstance().GetCallingUid();
    if (uid == 0) {
        CONCUR_LOGE("Uid is 0, error query");
        return;
    }
    switch (queryItem) {
        case QUERY_UI:
            QueryUi(uid, queryRs);
            break;
        case QUERY_RENDER:
            QueryRender(uid, queryRs);
            break;
        case QUERY_RENDER_SERVICE:
            QueryRenderService(uid, queryRs);
            break;
        case QUERY_HWC:
            QueryHwc(uid, queryRs);
            break;
        default:
            break;
    }
    return;
}

void TaskController::QueryUi(int uid, IntervalReply& queryRs)
{
    if (uid == SYSTEM_UID) {
        return;
    }
    pid_t pid = IPCSkeleton::GetInstance().GetCallingPid();
    auto iter = GetRecordOfUid(uid);
    if (iter == m_foregroundApp.end()) {
        CONCUR_LOGD("Query ui with uid %{public}d failed: pid %{public}d", uid, pid);
        return;
    }
    int grpId = iter->GetGrpId();
    if (grpId <= 0) {
        CONCUR_LOGI("%{public}d Query ui with none grpid", uid);
        queryRs.rtgId = -1;
    } else {
        queryRs.rtgId = grpId;
    }
    return;
}

void TaskController::QueryRender(int uid, IntervalReply& queryRs)
{
    if (uid == SYSTEM_UID) {
        return;
    }
    pid_t pid = IPCSkeleton::GetInstance().GetCallingPid();
    auto iter = GetRecordOfUid(uid);
    if (iter == m_foregroundApp.end()) {
        CONCUR_LOGD("Query render with uid %{public}d failed, pid %{public}d", uid, pid);
        return;
    }
    int grpId = iter->GetGrpId();
    if (grpId <= 0) {
        CONCUR_LOGI("%{public}d Query render with none grpid", uid);
        queryRs.rtgId = -1;
    } else {
        queryRs.rtgId = grpId;
    }
    return;
}

void TaskController::QueryRenderService(int uid, IntervalReply& queryRs)
{
    if (m_renderServiceGrp > 0) {
        CONCUR_LOGD("uid %{public}d query rs group %{public}d.", uid, m_renderServiceGrp);
        queryRs.rtgId = m_renderServiceGrp;
        return;
    }
    TryCreateRsGroup();
    queryRs.rtgId = m_renderServiceGrp;
    CONCUR_LOGE("uid %{public}d query rs group failed and create %{public}d.", uid, m_renderServiceGrp);
    return;
}

void TaskController::QueryHwc(int uid, IntervalReply& queryRs)
{
    if (uid == SYSTEM_UID) {
        return;
    }
    pid_t pid = IPCSkeleton::GetInstance().GetCallingPid();
    auto iter = GetRecordOfUid(uid);
    if (iter == m_foregroundApp.end()) {
        CONCUR_LOGD("Query hwc with uid %{public}d failed, pid %{public}d", uid, pid);
        return;
    }
    int grpId = iter->GetGrpId();
    if (grpId <= 0) {
        CONCUR_LOGI("%{public}d Query hwc with none grpid", uid);
        queryRs.rtgId = -1;
    } else {
        queryRs.rtgId = grpId;
    }
    return;
}

void TaskController::Init()
{
    TypeMapInit();
    m_qosManager.Init();
    TryCreateRsGroup();
}

void TaskController::TypeMapInit()
{
    m_MsgType.clear();
    m_MsgType.insert(pair<std::string, int>("foreground", MSG_FOREGROUND));
    m_MsgType.insert(pair<std::string, int>("background", MSG_BACKGROUND));
    m_MsgType.insert(pair<std::string, int>("appStart", MSG_APP_START));
    m_MsgType.insert(pair<std::string, int>("appKilled", MSG_APP_KILLED));
    m_MsgType.insert(pair<std::string, int>("focus", MSG_FOCUS));
    m_MsgType.insert(pair<std::string, int>("renderReg", MSG_REG_RENDER));
    m_MsgType.insert(pair<std::string, int>("uiReg", MSG_REG_UI));
    m_MsgType.insert(pair<std::string, int>("keyThread", MSG_REG_KEY_THERAD));
}

void TaskController::TryCreateRsGroup()
{
    if (!m_rtgEnabled) {
        m_rtgEnabled = EnableRtg(true) < 0 ? false : true;
        if (!m_rtgEnabled) {
            CONCUR_LOGE("Rtg enable failed");
            return;
        }
        CONCUR_LOGI("Enable Rtg");
    }
    m_renderServiceGrp = CreateNewRtgGrp(PRIO_RT, MAX_KEY_THREADS);
    if (m_renderServiceGrp <= 0) {
        CONCUR_LOGI("CreateRsRtgGroup with RT failed, try change to normal type.");
        m_renderServiceGrp = CreateNewRtgGrp(PRIO_NORMAL, MAX_KEY_THREADS);
    }
    if (m_renderServiceGrp <= 0) {
        CONCUR_LOGI("CreateRsRtgGroup failed! rtGrp:%{public}d", m_renderServiceGrp);
    }
}

int TaskController::GetRequestType(std::string strRequstType)
{
    auto iter = m_MsgType.find(strRequstType);
    if (iter == m_MsgType.end()) {
        return MSG_TYPE_MAX;
    }
    return m_MsgType[strRequstType];
}

bool TaskController::CheckUid(pid_t uid)
{
    if ((uid != SYSTEM_UID) && (uid != 0)) {
        return false;
    }
    return true;
}

void TaskController::DealSystemRequest(int requestType, const Json::Value& payload)
{
    int appUid = stoi(payload["uid"].asString());
    if (appUid < 0) {
        CONCUR_LOGE("appUid error:%d", appUid);
        return;
    }
    switch (requestType) {
        case MSG_FOREGROUND:
            NewForeground(appUid);
            break;
        case MSG_BACKGROUND:
            NewBackground(appUid);
            break;
        case MSG_APP_START:
            NewAppStart(appUid);
            break;
        case MSG_APP_KILLED:
            AppKilled(appUid);
            break;
        default:
            CONCUR_LOGE("Unknown system request");
            break;
    }
}

void TaskController::DealAppRequest(int requestType, const Json::Value& payload, pid_t uid)
{
    if (uid <= SYSTEM_UID) {
        CONCUR_LOGE("Deal app request with system uid");
        std::string strUid = payload["uid"].asString();
        if (uid == 0) { // Here is used for dealing with pid invalid situation
            uid = stoi(strUid);
        }
    }
    std::string strTid = payload["tid"].asString();
    std::string strPrio = "0";

    int tid = stoi(strTid);
    if ((requestType >= MSG_REG_RENDER) && (requestType <= MSG_REG_KEY_THERAD)) {
        int prioType = PRIO_NORMAL;
        auto record = GetRecordOfUid(uid);
        if (record == m_foregroundApp.end()) {
            return;
        }
        if (requestType != MSG_REG_KEY_THERAD) {
            prioType = PRIO_RT;
        } else if ((requestType == MSG_REG_KEY_THERAD) && (stoi(strPrio))) {
            prioType = RPIO_IN;
        }
        record->AddKeyThread(tid, prioType);
    }
}

std::list<ForegroundAppRecord>::iterator TaskController::GetRecordOfUid(int uid)
{
    for (auto iter = m_foregroundApp.begin(); iter != m_foregroundApp.end(); iter++) {
        if (iter->GetUid() == uid) {
            return iter;
        }
    }
    return m_foregroundApp.end();
}

void TaskController::NewForeground(int uid)
{
    auto it = find(m_authApps.begin(), m_authApps.end(), uid);
    if (it == m_authApps.end()) {
        CONCUR_LOGI("un-authed uid %{public}d", uid);
        return;
    }
    unsigned int uidParam = static_cast<unsigned int>(uid);
    unsigned int uaFlag = AF_RTG_ALL;
    unsigned int status = AUTH_STATUS_FOREGROUND;

    int ret = AuthEnable(uidParam, uaFlag, status);
    if (ret) {
        CONCUR_LOGI("auth_enable fail with %{public}d", ret);
    } else {
        CONCUR_LOGI("auth_enable %{public}d success", uid);
    }
    bool found = false;
    for (auto iter = m_foregroundApp.begin(); iter != m_foregroundApp.end(); iter++) {
        if (iter->GetUid() == uid) {
            found = true;
            CONCUR_LOGI("uid %{public}d is already in foreground.", uid);
            iter->BeginScene();
        }
    }
    CONCUR_LOGI("uid %{public}d change to foreground.", uid);
    if (!found) {
        ForegroundAppRecord *tempRecord = new ForegroundAppRecord(uid);
        if (tempRecord->IsValid()) { // if grp id create failed, will not be valid
            m_foregroundApp.push_back(*tempRecord);
            tempRecord->BeginScene();
        } else {
            delete tempRecord;
        }
    }
}

void TaskController::NewBackground(int uid)
{
    auto it = find(m_authApps.begin(), m_authApps.end(), uid);
    if (it == m_authApps.end()) {
        CONCUR_LOGI("un-authed uid %{public}d", uid);
        return;
    }
    CONCUR_LOGI("uid %{public}d change to background.", uid);
    unsigned int uidParam = static_cast<unsigned int>(uid);

    int ret = AuthPause(uidParam);
    if (ret) {
        CONCUR_LOGI("auth_pause fail with %d", ret);
    } else {
        CONCUR_LOGI("auth_pause %{public}d success", ret);
    }
    for (auto iter = m_foregroundApp.begin(); iter != m_foregroundApp.end(); iter++) {
        if (iter->GetUid() == uid) {
            iter->EndScene();
            return;
        }
    }
}

void TaskController::NewAppStart(int uid)
{
    CONCUR_LOGI("uid %{public}d start.", uid);
    unsigned int uidParam = static_cast<unsigned int>(uid);
    unsigned int uaFlag = AF_RTG_ALL;
    unsigned int status = AUTH_STATUS_BACKGROUND;

    int ret = AuthEnable(uidParam, uaFlag, status);
    if (ret) {
        CONCUR_LOGI("auth_enable fail with %d", ret);
    } else {
        CONCUR_LOGI("auth_enable %{public}d success", uid);
    }
    m_authApps.push_back(uid);
}

void TaskController::AppKilled(int uid)
{
    CONCUR_LOGI("uid %{public}d killed.", uid);
    unsigned int uidParam = static_cast<unsigned int>(uid);
    int ret = AuthDelete(uidParam);
    if (ret) {
        CONCUR_LOGI("auth_delete fail with %d", ret);
    } else {
        CONCUR_LOGI("auth_delete %{public}d success", uid);
    }
    for (auto iter = m_foregroundApp.begin(); iter != m_foregroundApp.end(); iter++) {
        if (iter->GetUid() == uid) {
            m_foregroundApp.erase(iter++);
        }
    }
}

void TaskController::PrintInfo()
{
    for (auto iter = m_foregroundApp.begin(); iter != m_foregroundApp.end(); iter++) {
        iter->PrintKeyThreads();
    }
}

ForegroundAppRecord::ForegroundAppRecord(int uid)
{
    m_uid = uid;
    m_grpid = CreateNewRtgGrp(PRIO_RT, MAX_KEY_THREADS);
    if (m_grpid <= 0) {
        CONCUR_LOGI("CreateNewRtgGroup with RT failed, try change to normal type.");
        m_grpid = CreateNewRtgGrp(PRIO_NORMAL, MAX_KEY_THREADS);
    }
    if (m_grpid <= 0) {
        CONCUR_LOGI("CreateNewRtgGroup failed! rtGrp:%{public}d, pid: %{public}d", m_grpid, uid);
    }
}

ForegroundAppRecord::~ForegroundAppRecord()
{
    if (m_grpid > 0) {
        DestroyRtgGrp(m_grpid);
    }
}

void ForegroundAppRecord::AddKeyThread(int tid, int prio)
{
    int rtgPrio = (prio >= PRIO_NORMAL) ? PRIO_NORMAL : PRIO_RT;
    if (m_keyThreads.find(tid) != m_keyThreads.end()) {
        return;
    }
    if (m_grpid <= 0) {
        CONCUR_LOGI("Add key thread fail: Grp id not been created success.");
        return;
    }
    if (m_keyThreads.size() >= MAX_KEY_THREADS) {
        CONCUR_LOGI("Add key thread fail: Key threads num limit.");
        return;
    }
    if (prio == RPIO_IN) {
        setpriority(PRIO_PROCESS, tid, -13); // -13 represent spcial nice in qos
    } else {
        int ret = AddThreadToRtg(tid, m_grpid, rtgPrio);
        if (ret != 0) {
            CONCUR_LOGI("Add key thread fail: Kernel err report.");
        } else {
            CONCUR_LOGI("Add key thread %{public}d", tid);
        }
        m_keyThreads.insert(tid);
    }
}

bool ForegroundAppRecord::BeginScene()
{
    if (m_grpid <= 0) {
        CONCUR_LOGI("Error begin scene in uid %{public}d", m_uid);
        return false;
    }
    OHOS::RME::BeginFrameFreq(m_grpid, 0);
    OHOS::RME::EndFrameFreq(m_grpid);
    return true;
}

bool ForegroundAppRecord::EndScene()
{
    if (m_grpid <= 0) {
        CONCUR_LOGI("Error end scene in uid %{public}d", m_uid);
        return false;
    }
    OHOS::RME::EndScene(m_grpid);
    return true;
}

int ForegroundAppRecord::GetUid()
{
    return m_uid;
}

int ForegroundAppRecord::GetGrpId()
{
    return m_grpid;
}

bool ForegroundAppRecord::IsValid()
{
    if (m_uid > 0 && m_grpid > 0) {
        return true;
    }
    return false;
}

void ForegroundAppRecord::PrintKeyThreads()
{
    std::string strLog = "pid ";
    strLog.append(std::to_string(m_uid));
    strLog.append(" has key threads: ");
    for (auto iter = m_keyThreads.begin(); iter != m_keyThreads.end(); iter++) {
        std::string temp = std::to_string(*iter);
        strLog.append(temp);
        strLog.append(", ");
    }
    CONCUR_LOGD("%{public}s", strLog.c_str());
}

} // namespace ConcurrentTask
} // namespace OHOS